package helper.scxml.scxml2

import helper.base.A2LHM
import helper.base.LHMHelper.A3LHM
import helper.base.StringHelper.removeEmptyLine
import helper.base.list.ArrayListHelper.ArrayListExpand.calMean
import helper.base.list.ArrayListHelper.ArrayListExpand.toCovarianceMatrix
import helper.base.list.ArrayListHelper.ArrayListExpandToStrExpand.toD10Str
import helper.base.list.DoubleList
import helper.base.list.DoubleListList
import helper.base.math.DoubleMatrixHelper.DoubleMatrix
import helper.base.math.DoubleMatrixHelper.DoubleMatrix.Expand.toDoubleMatrix
import helper.base.string.ToStrHelper.IToStr
import helper.base.string.ToStrHelper.TabStringUnit.Companion.makeTabStringUnit
import helper.base.string.ToStrHelper.toSBStr
import helper.scxml.scxml2.DistanceHelper.getDistanceToMatrix
import helper.scxml.scxml2.DistanceHelper.getEuclideanDistance
import helper.scxml.scxml2.DistanceHelper.getWeightFromDistance
import helper.scxml.scxml2.MathHelper.DoubleArrayArrayExpand.toDoubleArrayArray
import org.apache.commons.math3.linear.Array2DRowRealMatrix
import org.apache.commons.math3.linear.ArrayRealVector

object MathHelper {
    object DoubleArrayListExpand {
        fun List<Double>.toArrayRealVector(
        ): ArrayRealVector {
            return ArrayRealVector(
                this.toDoubleArray()
            )
        }
    }

    object LHMToExpand {
        fun A2LHM<String, String>.toDoubleArrayList(
            keys: List<String>,
        ): DoubleList {
            val v = ArrayList<Double>()
            keys.forEach {
                v.add(
                    this[it]!!.toDouble()
                )
            }
            return v
        }
    }

    class DSSUnit(
        val dss: DoubleListList,
        val doDoubleMatrixFun: DoubleMatrix.() -> DoubleMatrix,
    ) : IToStr {
        companion object {
            fun getDSSUnit(
                dss: DoubleListList = ArrayList(),
                doDoubleMatrixFun: DoubleMatrix.() -> DoubleMatrix = {
                    this
                },
            ): DSSUnit {
                return DSSUnit(
                    dss,
                    doDoubleMatrixFun,
                )
            }
        }

        object Expand {
            fun ArrayList<DoubleList>.toDSSUnit(
            ): DSSUnit {
                return getDSSUnit(
                    this,
                )
            }

            fun DoubleListList.toArray2DRowRealMatrix(
            ): Array2DRowRealMatrix {
                return Array2DRowRealMatrix(
                    this.toDoubleArrayArray()
                )
            }
        }

        val mean: DoubleList by lazy {
            dss.calMean()
        }

        val covarianceMatrix: DoubleMatrix by lazy {
            this.dss.toCovarianceMatrix().toDoubleMatrix().doDoubleMatrixFun()
        }

        fun getWeightOf(
            ds: DoubleList,
            getDistanceFun: DSSUnit.(DoubleList) -> Double,
        ): Double {
            return getWeightFromDistance(
                dss.size,
                this.getDistanceFun(ds),
            )
        }

        fun getDistanceToMean(
            ds: DoubleList,
        ): Double {
            val debug = false
            val euclideanDistance = getEuclideanDistance(
                ds,
                mean,
            )
            if (debug) {
                println("fun getDistanceToMean()=${euclideanDistance}")
            }
            return euclideanDistance
        }

        fun getDistanceToCovarianceMatrix(
            ds: DoubleList,
        ): Double {
            return getDistanceToMatrix(
                ds,
                mean,
                covarianceMatrix.matrix,
            )
        }

        override fun toStr(tabNum: Int): String {
            val sb = StringBuilder()
            val tsu = makeTabStringUnit(tabNum)
            sb.append("${tsu.tabNumStr0}DSSUnit(\n")

            sb.append("${tsu.tabNumStr1}covarianceMatrix=\n")
            sb.append(covarianceMatrix.matrix.toD10Str(2))

            sb.append("${tsu.tabNumStr0})\n")
            return sb.toString().removeEmptyLine()
        }

        fun toT1Str(tabNum: Int): String {
            val sb = StringBuilder()
            val tsu = makeTabStringUnit(tabNum)
            sb.append("${tsu.tabNumStr0}DSSUnit(\n")
            dss.toSBStr(sb, tsu, "dss", 1)

            sb.append("${tsu.tabNumStr0})\n")
            return sb.toString().removeEmptyLine()
        }

        fun toT2Str(tabNum: Int): String {
            val sb = StringBuilder()
            val tsu = makeTabStringUnit(tabNum)
            sb.append("${tsu.tabNumStr0}DSSUnit(\n")

            sb.append("${tsu.tabNumStr1}dss=\n")
            sb.append(dss.toD10Str(2))

            sb.append("${tsu.tabNumStr1}covarianceMatrix=\n")
            sb.append(covarianceMatrix.matrix.toD10Str(2))

            sb.append("${tsu.tabNumStr0})\n")
            return sb.toString().removeEmptyLine()
        }
    }

    class LocationEventVListLHM : A3LHM<String, String, DSSUnit>() {
        companion object {
            fun makeLocationEventVListLHM(
            ): LocationEventVListLHM {
                return LocationEventVListLHM()
            }
        }
    }

    object DoubleArrayArrayExpand {
        fun DoubleListList.toDoubleArrayArray(
        ): Array<DoubleArray> {
            return Array(this.size) { i ->
                DoubleArray(this[i].size) { j ->
                    this[i][j]
                }
            }
        }
    }
}